package lumis.util;

import lumis.portal.PortalException;
import lumis.portal.UnexpectedException;
import lumis.portal.stability.StableMinor;

import java.io.File;
import java.io.StringReader;
import java.io.StringWriter;

import javax.servlet.http.HttpServletRequest;
import javax.xml.parsers.*;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.pool.BasePoolableObjectFactory;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.apache.xml.serialize.*;
import org.w3c.dom.*;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

/**
 * Utility file for xml
 *
 * @version $Revision: 13082 $ $Date: 2011-05-27 16:03:12 -0300 (Fri, 27 May 2011) $
 * @since 4.0.0
 */
@StableMinor(version = "6.0", sinceVersion = "4.0")
public class XmlUtil
{
	public static final String XML_HEADER = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
	private static DocumentBuilderFactory documentBuilderFactory;
	
	// this variable's type is Object for a workaround for the tomcat's WebAppClassLoader not
	// being released after application reload when this variable is set to byte[] type.
	// In Java 6 this variable could be byte[] with no problem.
	private static final Object UTF8Header = new byte[]{ (byte) 0xEF, (byte) 0xBB, (byte) 0xBF };

	private static final String XPATH_CONDITION_OPERATOR_EQUALS = "=";
	private static final String XPATH_CONDITION_OPERATOR_LESSTHAN = "<";
	private static final String XPATH_CONDITION_OPERATOR_GREATERTHAN = ">";

	private static final String XPATH_CONCATENATION_OPERATOR_AND = " and ";
	private static final String XPATH_CONCATENATION_OPERATOR_OR = " or ";
	private static Pattern encodingPattern = Pattern.compile("<\\? ?xml [^>]*encoding=\"([^\"]*)\"[^>]*\\?>"); 
	
	static
	{
		try
		{
			documentBuilderFactory = DocumentBuilderFactory.newInstance();
			documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
			documentBuilderFactory.setAttribute("http://apache.org/xml/properties/input-buffer-size", new Integer(8192));
			documentBuilderFactory.setFeature("http://apache.org/xml/features/dom/defer-node-expansion", false);
			documentBuilderFactory.setNamespaceAware(true);
		}
		catch (Exception e)
		{
			throw new RuntimeException("Error initializing DocumentBuilderFactory", e);
		}
	}

	/**
	 * Pool of {@link DocumentBuilder} instances.
	 * @since 5.5.0
	 */
	private static GenericObjectPool documentBuilderPool = new GenericObjectPool(
			new BasePoolableObjectFactory()
			{
				@Override
				public Object makeObject() throws Exception
				{
					return documentBuilderFactory.newDocumentBuilder();
				}
		
				@Override
				public void passivateObject(Object obj) throws Exception
				{
					((DocumentBuilder)obj).reset();
				}
			});
	
	/**
	 * <strong>Internal use only.</strong>
	 * <p>
	 * This method may be incompatible in future versions.
	 * 
	 * @param minSize
	 *            minimum pool size.
	 * @param maxSize
	 *            maximum pool size.
	 * @param timeBetweenEvictionRunsMillis
	 *            the number of milliseconds to sleep between runs of the idle
	 *            object evictor thread. When non-positive, no idle object
	 *            evictor thread will be run.
	 * @param numTestsPerEvictionRun
	 *            the number of objects to examine during each run of the idle
	 *            object evictor thread (if any).
	 * @param minEvictableIdleTimeMillis
	 *            The minimum amount of time an object may sit idle in the pool
	 *            before it is eligable for eviction by the idle object evictor
	 *            (if any).
	 * @since 5.5.0
	 */
	public static void initializeDocumentBuilderPool(int minSize, int maxSize, 
			long timeBetweenEvictionRunsMillis, int numTestsPerEvictionRun,
			int minEvictableIdleTimeMillis)
	{
		try
		{
			synchronized(documentBuilderPool)
			{
				documentBuilderPool.clear();
				documentBuilderPool.setMaxActive(maxSize);
				documentBuilderPool.setMaxIdle(-1);
				documentBuilderPool.setMinIdle(minSize);
				documentBuilderPool.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
				documentBuilderPool.setNumTestsPerEvictionRun(numTestsPerEvictionRun);
				documentBuilderPool.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
				
				// create minSize objects
				for (int i=0; i<minSize; i++)
					documentBuilderPool.addObject();
			}
		}
		catch (Exception e)
		{
			throw new RuntimeException("Error initializing DocumentBuilder pool", e);
		}
	}

	public static DocumentBuilder getXmlDocumentBuilder() throws PortalException
	{
		try
		{
			return documentBuilderFactory.newDocumentBuilder();
		}
		catch(ParserConfigurationException e)
		{
			throw new UnexpectedException(e);
		}
	}

	public static Document getDocument(File xmlFile) throws PortalException
	{
		try
		{
			DocumentBuilder documentBuilder = (DocumentBuilder)documentBuilderPool.borrowObject();
			try
			{
				return documentBuilder.parse(xmlFile);
			}
			finally
			{
				documentBuilderPool.returnObject(documentBuilder);
			}
		}
		catch (Exception e)
		{
			throw new UnexpectedException(e);
		}
	}

	public static Document getDocument(HttpServletRequest request) throws Exception
	{
		DocumentBuilder documentBuilder = (DocumentBuilder)documentBuilderPool.borrowObject();
		try
		{
			return documentBuilder.parse(request.getInputStream());
		}
		finally
		{
			documentBuilderPool.returnObject(documentBuilder);
		}
	}

	public static Document getDocument(String xmlString) throws PortalException
	{
		try
		{
			DocumentBuilder documentBuilder = (DocumentBuilder)documentBuilderPool.borrowObject();
			try
			{
				return documentBuilder.parse(new InputSource(new StringReader(xmlString)));
			}
			catch (SAXException e)
			{
			 	throw createPortalException(e);
			}
			finally
			{
				documentBuilderPool.returnObject(documentBuilder);
			}
		}
		catch (Exception e)
		{
			throw new UnexpectedException(e);
		}
	}

	/**
	 * Creates a PortalException based on the given SAXException.
	 * The PortalException created contains a message describing the
	 * error, according to the information in the SAXException. 
	 * @param e the SAXException
	 * @return the PortalException created.
	 * @since 4.0.8
	 */
	public static PortalException createPortalException(SAXException e)
	{
		if (e instanceof SAXParseException)
		{
			SAXParseException parseException = (SAXParseException)e;
			return new PortalException("STR_INVALID_XML_AT_LINE_COL;" + parseException.getLineNumber() + ";" + 
					parseException.getColumnNumber() + ";" + TextUtil.escapeLocalizationParameter(parseException.getMessage()), parseException);
		}
		else
			return new PortalException("STR_INVALID_XML;" + TextUtil.escapeLocalizationParameter(e.getMessage()), e);
	}

	/**
	 * Returns the xml string representation for a node.
	 * @param node the node. Must be of type {@link Document} or {@link Element}.
	 * @return the xml string.
	 * @since 4.2.0
	 */
	public static String getXmlString(Node node) throws PortalException
	{
		if (node instanceof Document)
		{
			try
			{
				return getXmlString((Document)node);
			}
			catch (Exception e)
			{
				throw new UnexpectedException(e);
			}
		}
		else if (node instanceof Element)
		{
			return getXmlString((Element)node);
		}
		else
		{
			throw new IllegalArgumentException("Only nodes of type Element or Document are supported");
		}
	}
	
	public static String getXmlString(Element element) throws PortalException
	{
		try
		{
			OutputFormat format = new OutputFormat(element.getOwnerDocument());
			format.setOmitXMLDeclaration(true);
			format.setOmitComments(true);
			format.setPreserveSpace(true);
			format.setEncoding("UTF-8");
			StringWriter stringWriter = new StringWriter();
			XMLSerializer xmlSerializer = new XMLSerializer(stringWriter, format);
			xmlSerializer.serialize(element);
	
			return stringWriter.toString();
		}
		/*catch(PortalException e)
		{
			throw e;
		}*/
		catch(Exception e)
		{
			throw new UnexpectedException(e);
		}
	}

	public static String getXmlString(Document document) throws Exception
	{
		OutputFormat format = new OutputFormat(document);
		format.setOmitXMLDeclaration(true);
		format.setOmitComments(true);
		format.setIndenting(true);
		format.setEncoding("UTF-8");
		StringWriter stringWriter = new StringWriter();
		XMLSerializer xmlSerializer = new XMLSerializer(stringWriter, format);
		xmlSerializer.serialize(document);

		return stringWriter.toString();
	}

	public static boolean readNodeBoolean(String xpathExpression, Node sourceNode, boolean valueForNull) throws PortalException
	{
		String nodeString = readNodeString(xpathExpression, sourceNode, null);
		if (nodeString != null)
			return Boolean.valueOf(nodeString).booleanValue();
		else
			return valueForNull;
	}

	public static boolean readAttributeBoolean(String attributeName, Node node) throws PortalException
	{
		return readAttributeBoolean(attributeName, node, false);
	}

	public static boolean readAttributeBoolean(String attributeName, Node node, boolean valueForNull) throws PortalException
	{
		try
		{
			Node attributeNode = node.getAttributes().getNamedItem(attributeName);

			if (attributeNode == null)
				return valueForNull;
			else if (attributeNode.getNodeValue().equals("true"))
				return true;
			else
				return false;
		}
		catch (Exception e)
		{
			throw new UnexpectedException(e);
		}
	}

	public static int readNodeInt(String xpathExpression, Node sourceNode, int valueForNull) throws PortalException
	{
		String nodeString = readNodeString(xpathExpression, sourceNode, null);
		if (nodeString != null)
			return Integer.parseInt(nodeString);
		else
			return valueForNull;
	}

	public static Integer readNodeInteger(String xpathExpression, Node sourceNode, Integer valueForNull) throws PortalException
	{
		String nodeString = readNodeString(xpathExpression, sourceNode, null);
		if (nodeString != null)
			return new Integer(nodeString);
		else
			return valueForNull;
	}

	public static String readAttributeString(String attributeName, Node node) throws PortalException
	{
		try
		{
			return readAttributeString(attributeName, node, null);
		}
		catch (PortalException e)
		{
			throw e;
		}
		catch (Exception e)
		{
			throw new UnexpectedException(e);
		}
	}
	
	/**
	 * Returns the attribute integer value in the given node.
	 *   
	 * @param attributeName
	 * @param node
	 * @param valueForNull
	 * @return
	 * @throws PortalException
	 */
	public static int readAttributeInt(String attributeName, Node node, int valueForNull) throws PortalException
	{
		String nodeString = readAttributeString(attributeName, node, null);
		if (nodeString != null)
			return Integer.parseInt(nodeString);
		else
			return valueForNull;
	}

	public static String readAttributeString(String attributeName, Node node, String valueForNull) throws PortalException
	{
		Node attributeNode = node.getAttributes().getNamedItem(attributeName);

		if (attributeNode != null)
			return getTextContent(attributeNode);
		else
			return valueForNull;
	}

	public static String getTextContent(Node node)
	{
		if (node != null)
		{
			StringBuilder result = new StringBuilder();
			Node child = node.getFirstChild();
			while (child != null)
			{
				String value = child.getNodeValue();
				if (value != null)
					result.append(value);
				child = child.getNextSibling();
			}
			return result.toString();
		}
		else
			return "";
	}
	
	public static void setTextContent(Node parentNode, String textValue)
	{
		Node firstChild = parentNode.getFirstChild();
		if(firstChild == null)
			parentNode.appendChild(parentNode.getOwnerDocument().createTextNode(textValue));
		else
		{
			if(firstChild.getNodeType() != Node.TEXT_NODE && firstChild.getNodeType() != Node.CDATA_SECTION_NODE)
				throw new RuntimeException("STR_SETTING_TEXT_VALUE_TO_NON_TEXT_NODE");
			firstChild.setNodeValue(textValue);
		}
	}
	
	public static String readNodeString(String xpathExpression, Node sourceNode) throws PortalException
	{
		return readNodeString(xpathExpression, sourceNode, null);
	}

	public static String readNodeString(String xpathExpression, Node sourceNode, String valueForNull) throws PortalException
	{
		Node pNode = XmlUtil.selectSingleNode(xpathExpression, sourceNode);
		if (pNode != null)
		{
			String result = getTextContent(pNode);
			if (result == null || result.length() == 0)
				return valueForNull;
			else
				return result;
		}
		else
		{
			return valueForNull;
		}
	}

	public static String[] readNodesString(String xpathExpression, Node sourceNode) throws PortalException
	{
		Node[] pNodeList = XmlUtil.selectNodes(xpathExpression, sourceNode);

		int numItems = pNodeList.length;

		String[] nodesText = new String[numItems];

		for (int i = 0; i < numItems; i++)
			nodesText[i] = XmlUtil.getTextContent(pNodeList[i]);

		return nodesText;
	}

	public static boolean nodeExists(String xpathExpression, Node sourceNode) throws PortalException
	{
		return (XmlUtil.selectSingleNode(xpathExpression, sourceNode) != null);
	}

	public static Element addTextNode(Node xmlParentNode, String newNodeName, String newNodeText) throws PortalException
	{
		Element newNode = xmlParentNode.getOwnerDocument().createElement(newNodeName);

		// TODO Ariel: This is deleting all the new line, carriage return
		// characters. It should not.
		setTextContent(newNode, newNodeText); // default stringsUrlPrefix
		xmlParentNode.appendChild(newNode);
		return newNode;

	}
	
	public static Element addTextNode(Node xmlParentNode, String xpathExpressionParentNode, String newNodeName, String newNodeText) throws PortalException
	{
		Node parentNode = XmlUtil.getOrCreateNode(xpathExpressionParentNode, xmlParentNode);
		return addTextNode(parentNode, newNodeName, newNodeText);
	}

	public static Node[] addTextNodes(Node xmlParentNode, String newNodesName, String[] newNodesValue) throws PortalException
	{
		Node[] newNodes = new Node[newNodesValue.length];
		for (int i = 0; i < newNodesValue.length; i++)
			newNodes[i] = addTextNode(xmlParentNode, newNodesName, newNodesValue[i]);
		return newNodes;
	}

	public static Node[] addTextNodes(Node xmlParentNode, String xpathExpressionParentNode, String newNodesName, String[] newNodesValue) throws Exception
	{
		Node parentNode = XmlUtil.getOrCreateNode(xpathExpressionParentNode, xmlParentNode);
		Node[] newNodes = new Node[newNodesValue.length];
		for (int i = 0; i < newNodesValue.length; i++)
			newNodes[i] = addTextNode(parentNode, newNodesName, newNodesValue[i]);
		return newNodes;
	}

	public static Node addNode(Node xmlParentNode, String newNodeName)
	{
		Node newNode = xmlParentNode.getOwnerDocument().createElement(newNodeName);
		xmlParentNode.appendChild(newNode);
		return newNode;
	}

	public static Node addNode(Node xmlParentNode, String xpathExpressionParentNode, String newNodeName) throws PortalException
	{
		try
		{
			Node parentNode = XmlUtil.selectSingleNode(xpathExpressionParentNode, xmlParentNode);
			return addNode(parentNode, newNodeName);
		}
		catch(PortalException e)
		{
			throw e;
		}
		catch(Exception e)
		{
			throw new UnexpectedException(e);
		}
	}

	public static Attr addNodeAttr(Node xmlParentNode, String attributeName)
	{
		return addNodeAttr(xmlParentNode, attributeName, null);
	}

	public static Attr addNodeAttr(Node xmlParentNode, String attributeName, String value)
	{
		Attr attr = xmlParentNode.getOwnerDocument().createAttribute(attributeName);
		if (value != null)
			setTextContent(attr, value);

		xmlParentNode.getAttributes().setNamedItem(attr);
		return attr;
	}

	public static Node copyNode(Node xmlNodeFrom, Node xmlNodeTo)
	{
		return xmlNodeTo.appendChild(xmlNodeFrom.cloneNode(true));
	}

	public static Node copyNode(Node xmlNodeFrom, String xpathExpressionFrom, Node xmlNodeTo) throws Exception
	{
		Node nodeFrom = XmlUtil.selectSingleNode(xpathExpressionFrom, xmlNodeFrom);
		return copyNode(nodeFrom, xmlNodeTo);
	}

	public static Node copyNode(Node xmlNodeFrom, Node xmlNodeTo, String xpathExpressionTo) throws Exception
	{
		Node nodeTo = XmlUtil.selectSingleNode(xpathExpressionTo, xmlNodeTo);
		return copyNode(xmlNodeFrom, nodeTo);
	}

	public static Node copyNode(Node xmlNodeFrom, String xpathExpressionFrom, Node xmlNodeTo, String xpathExpressionTo) throws Exception
	{
		Node nodeFrom = XmlUtil.selectSingleNode(xpathExpressionFrom, xmlNodeFrom);
		Node nodeTo = XmlUtil.selectSingleNode(xpathExpressionTo, xmlNodeTo);
		return copyNode(nodeFrom, nodeTo);
	}

	public static Document getDocumentFromFile(String filePath) throws Exception
	{
		File configFile = new File(filePath);
		DocumentBuilder documentBuilder = XmlUtil.getXmlDocumentBuilder();
		Document serviceConfig = documentBuilder.parse(configFile);

		return serviceConfig;
	}

	public static Node selectSingleAncestorNode(String ancestorNameQuery, Node childNode) throws Exception
	{
		Node currentNode, candidateNode;
		while ((currentNode = childNode.getParentNode()) != null)
			if ((candidateNode = selectSingleNode(ancestorNameQuery, currentNode)) != null)
				return candidateNode;
		return null;
	}

	public static Node[] selectAncestorNodes(String ancestorNameQuery, Node childNode) throws Exception
	{
		List<Node> nodesList = new ArrayList<Node>();
		selectAncestorNodes(ancestorNameQuery, childNode, nodesList);
		Node[] nodes = new Node[nodesList.size()];
		return nodesList.toArray(nodes);
	}

	private static void selectAncestorNodes(String ancestorNameQuery, Node childNode, List<Node> nodes) throws Exception
	{
		Node[] candidateNodes;
		while ((childNode = childNode.getParentNode()) != null)
		{
			candidateNodes = selectNodes(ancestorNameQuery, childNode);
			for (Node candidateNode : candidateNodes)
				nodes.add(candidateNode);
		}
	}
	
	/**
	 * Clones a node, and adopts it into a new document.
	 * 
	 * @param originalNode
	 * @return
	 * @throws PortalException
	 * 
	 * @since 4.0.6
	 */
	public static Node importIntoNewDocument(Node originalNode) throws PortalException
	{
		DocumentBuilder documentBuilder = XmlUtil.getXmlDocumentBuilder();
		Document newDocument = documentBuilder.newDocument();
		
		Node newNode = newDocument.importNode(originalNode, true);
		
		return newDocument.appendChild(newNode);
	}

	public static Node selectSingleNode(String childNameQuery, Node parentNode) throws PortalException
	{
		try
		{
			if (childNameQuery.startsWith("/"))
			{
				parentNode = (parentNode.getOwnerDocument() == null) ? parentNode : parentNode.getOwnerDocument();
				childNameQuery = childNameQuery.substring(1);
			}

			if (childNameQuery.indexOf("|") > -1)
			{
				String[] childNameSubQueries = childNameQuery.split("|");
				Node currentNode;
				for (String childNameSubQuery : childNameSubQueries)
					if ((currentNode = selectSingleNode(childNameSubQuery, parentNode)) != null)
						return currentNode;
			}

			if (childNameQuery.indexOf("/") > -1)
			{
				String localQuery = childNameQuery.substring(0, childNameQuery.indexOf("/")); 
				
				Node[] localNodes;
				Node selectedNode;
				localNodes = selectNodes(localQuery, parentNode);
				for(int i = 0; i < localNodes.length; i++)
				{
					selectedNode = selectSingleNode(childNameQuery.substring(childNameQuery.indexOf("/")+1), localNodes[i]);
					if(selectedNode != null)
					{
						return selectedNode;
					}
				}
				
				return null;
			}

			Node childNode = null;
			String childName = childNameQuery;
			String condition = null;

			if (childNameQuery.indexOf("[") > -1)
			{
				childName = childNameQuery.substring(0, childNameQuery.indexOf("["));
				condition = childNameQuery.substring(childNameQuery.indexOf("[") + 1, childNameQuery.length() - 1);
			}

			if (childName.length() > 1 && childName.substring(0, 1).equals("@"))
			{
				childNode = parentNode.getAttributes().getNamedItem(childName.substring(1));
			}
			else if (childName.equals("."))
			{
				childNode = parentNode;
			}
			else if (childName.equals(".."))
			{
				childNode = parentNode.getParentNode();
			}
			else
			{
				childNode = parentNode.getFirstChild();
				while (childNode != null)
				{
					if (childNode.getNodeName().equals(childName))
					{
						if (condition != null)
						{
							if (XmlUtil.checkCondition(childNode, condition))
								break;
						}
						else
							break;
					}
					childNode = childNode.getNextSibling();
				}
			}

			return childNode;
		}
		catch (PortalException e)
		{
			throw e;
		}
		catch (Exception e)
		{
			throw new UnexpectedException(e);
		}
	}

	public static Node[] selectNodes(String childNameQuery, Node parentNode) throws PortalException
	{
		try
		{
			List<Node> nodesVector = new ArrayList<Node>();
			selectNodes(childNameQuery, parentNode, nodesVector);
			Node[] nodes = new Node[nodesVector.size()];
			return nodesVector.toArray(nodes);
		}
		catch (PortalException e)
		{
			throw e;
		}
		catch (Exception e)
		{
			throw new UnexpectedException(e);
		}
	}

	private static void selectNodes(String childNameQuery, Node parentNode, List<Node> nodes) throws Exception
	{
		if (childNameQuery.startsWith("/"))
		{
			parentNode = parentNode.getOwnerDocument();
			childNameQuery = childNameQuery.substring(1);
		}

		if (childNameQuery.indexOf("|") > -1)
		{
			String[] childNameSubQueries = childNameQuery.split("|");
			for (String childNameSubQuery : childNameSubQueries)
				selectNodes(childNameSubQuery, parentNode, nodes);
		}

		if (childNameQuery.indexOf("/") > -1)
		{
			int posFirstSlash = childNameQuery.indexOf("/");
			String firstNodeName = childNameQuery.substring(0, posFirstSlash);
			String remainderXPath = childNameQuery.substring(posFirstSlash + 1);
			Node[] firstNodes = selectNodes(firstNodeName, parentNode);
			for (Node firstNode : firstNodes)
				selectNodes(remainderXPath, firstNode, nodes);
			return;
		}

		Node childNode = parentNode.getFirstChild();
		String childName = childNameQuery;
		String condition = null;

		if (childNameQuery.indexOf("[") > -1)
		{
			childName = childNameQuery.substring(0, childNameQuery.indexOf("["));
			condition = childNameQuery.substring(childNameQuery.indexOf("[") + 1, childNameQuery.length() - 1);
		}

		if (childName.equals("."))
		{
			childNode = parentNode;
			if (condition != null)
			{
				if (XmlUtil.checkCondition(childNode, condition))
					nodes.add(childNode);
			}
			else
			{
				nodes.add(childNode);
			}
			return;
		}

		while (childNode != null)
		{
			if (childNode.getNodeName().equals(childName))
			{
				if (condition != null)
				{
					if (XmlUtil.checkCondition(childNode, condition))
						nodes.add(childNode);
				}
				else
				{
					nodes.add(childNode);
				}
			}
			childNode = childNode.getNextSibling();
		}
	}

	public static boolean checkCondition(Node conditionNode, String condition) throws Exception
	{
		boolean returnValue = false;

		String concatinationOperator = null;

		int posConcatination = condition.lastIndexOf(XPATH_CONCATENATION_OPERATOR_AND);
		if (posConcatination != -1)
		{
			concatinationOperator = XPATH_CONCATENATION_OPERATOR_AND;
		}
		int pos = condition.lastIndexOf(XPATH_CONCATENATION_OPERATOR_OR);
		if (pos > posConcatination)
		{
			posConcatination = pos;
			concatinationOperator = XPATH_CONCATENATION_OPERATOR_OR;
		}

		if (concatinationOperator != null)
		{
			// evaluate the expression from left to right
			String leftConditions = condition.substring(0, posConcatination);
			String rightCondition = condition.substring(posConcatination + concatinationOperator.length());

			if (concatinationOperator.equals(XPATH_CONCATENATION_OPERATOR_OR))
				returnValue = checkCondition(conditionNode, leftConditions) || checkSingleCondition(conditionNode, rightCondition);
			else
				returnValue = checkCondition(conditionNode, leftConditions) && checkSingleCondition(conditionNode, rightCondition);
		}
		else
		{
			returnValue = checkSingleCondition(conditionNode, condition);
		}

		return returnValue;
	}

	public static boolean checkSingleCondition(Node conditionNode, String condition) throws Exception
	{
		boolean returnValue = false;

		String conditionOperator = XPATH_CONDITION_OPERATOR_EQUALS;

		condition = condition.trim();

		if (condition.indexOf(XPATH_CONDITION_OPERATOR_LESSTHAN) > -1)
			conditionOperator = XPATH_CONDITION_OPERATOR_LESSTHAN;
		else if (condition.indexOf(XPATH_CONDITION_OPERATOR_GREATERTHAN) > -1)
			conditionOperator = XPATH_CONDITION_OPERATOR_GREATERTHAN;

		// treat only conditions of attribute with equals operator for now
		String[] curCondition = condition.split(conditionOperator);

		String attributeName = curCondition[0];
		String attributeValue = curCondition[1];

		attributeValue = attributeValue.substring(1, attributeValue.length() - 1);

		if (attributeName.substring(0, 1).equals("@"))
		{
			attributeName = attributeName.substring(1);

			String actualAttributeValue = XmlUtil.readAttributeString(attributeName, conditionNode);
			if (actualAttributeValue == null)
				returnValue = false;
			else
				returnValue = checkCondition(actualAttributeValue, attributeValue, conditionOperator);
		}
		else if (attributeName.equals("name()"))
		{
			String actualAttributeValue = conditionNode.getNodeName();
			returnValue = checkCondition(actualAttributeValue, attributeValue, conditionOperator);
		}
		else
		{
			String actualElementValue = XmlUtil.readNodeString(attributeName, conditionNode);
			if (actualElementValue == null)
				returnValue = false;
			else
				returnValue = (actualElementValue != null && checkCondition(actualElementValue, attributeValue, conditionOperator));
		}

		return returnValue;
	}

	protected static boolean checkCondition(String leftValue, String rightValue, String conditionOperator)
	{
		boolean returnValue = false;

		if (conditionOperator.equals(XmlUtil.XPATH_CONDITION_OPERATOR_GREATERTHAN))
			returnValue = Integer.parseInt(leftValue) > Integer.parseInt(rightValue);
		else if (conditionOperator.equals(XmlUtil.XPATH_CONDITION_OPERATOR_LESSTHAN))
			returnValue = Integer.parseInt(leftValue) < Integer.parseInt(rightValue);
		else
			returnValue = leftValue.equals(rightValue);

		return returnValue;
	}

	protected static Node selectSingleNodeRecursiveByName(String nodeName, Node parentNode) throws Exception
	{
		if (parentNode.getNodeName().equals(nodeName))
			return parentNode;
		NodeList subNodes = parentNode.getChildNodes();
		Node currentSubNode;
		for (int i = 0; i < subNodes.getLength(); i++)
		{
			currentSubNode = selectSingleNodeRecursiveByName(nodeName, subNodes.item(i));
			if (currentSubNode != null)
				return currentSubNode;
		}
		return null;
	}

	/**
	 * @deprecated since 4.2.0 replaced by {@link #getDocument(String)}.
	 */
	@Deprecated
	public static Document getXMLDocumentFromString(String xml) throws PortalException
	{
		return getDocument(xml);
	}

	/**
	 * @deprecated Since 4.2.0, this method is no longer used 
	 * and may be removed in the future.
	 */
	@Deprecated
	public static byte[] convertToUTFBytes(byte[] content)
	{
		byte xmlBytes[] = new byte[((byte[])UTF8Header).length + content.length];
		System.arraycopy(UTF8Header, 0, xmlBytes, 0, ((byte[])UTF8Header).length);
		System.arraycopy(content, 0, xmlBytes, ((byte[])UTF8Header).length, content.length);

		return xmlBytes;
	}

	public static Node getOrCreateNode(String path, Node parentNode) throws PortalException
	{
		Node result = selectSingleNode(path, parentNode);

		if (result == null)
		{
			int index = path.lastIndexOf("/");

			if (index != -1)
			{
				String parentPath = path.substring(0, index);

				Node curParentNode = getOrCreateNode(parentPath, parentNode);
				String childNodeName = path.substring(index + 1);

				result = curParentNode.appendChild(curParentNode.getOwnerDocument().createElement(childNodeName));
			}
			else
				result = parentNode.appendChild(parentNode.getOwnerDocument().createElement(path));
		}

		return result;
	}

	public static Node writeNodeString(String xpathExpression, Node sourceNode, String nodeValue) throws PortalException
	{
		Node node = getOrCreateNode(xpathExpression, sourceNode);
		setTextContent(node, nodeValue);
		return node;
	}

	public static String readAttributeOrNodeString(Node node, String fieldName) throws PortalException
	{
		return readAttributeOrNodeString(node, fieldName, null);
	}

	public static String readAttributeOrNodeString(Node node, String fieldName, String valueForNull) throws PortalException
	{
		String value = XmlUtil.readAttributeString(fieldName, node);

		if (value == null)
			return XmlUtil.readNodeString(fieldName, node, valueForNull);
		else
			return value;
	}

	public static void replaceChildNode(Node parentNode, Node newChildNode) throws Exception
	{
		String childNodeName = newChildNode.getNodeName();
		Node curNode = selectSingleNode(childNodeName, parentNode);
		if (curNode != null)
			parentNode.removeChild(curNode);

		parentNode.appendChild(newChildNode);
	}

	public static void replaceChildNode(String childNodeName, Node node, String xmlString) throws PortalException
	{
		try
		{
			Node curNode = selectSingleNode(childNodeName, node);
			if (curNode != null)
				node.removeChild(curNode);

			addNodeFromXmlString(node, xmlString);
		}
		catch (PortalException e)
		{
			throw e;
		}
		catch (Exception e)
		{
			throw new UnexpectedException(e);
		}
	}

	public static Node addNodeFromXmlString(Node node, String xmlString) throws PortalException
	{
		try
		{
			Document newDocument = XmlUtil.getDocument(xmlString);
			Document dataDoc = node.getOwnerDocument();
			return node.appendChild(dataDoc.importNode(newDocument.getDocumentElement(), true));
		}
		catch (PortalException e)
		{
			throw e;
		}
		catch (Exception e)
		{
			throw new UnexpectedException(e);
		}
	}

	// merges attributes from the source node to the destination Node. Only the
	// attributes that do not exist in the destination are copied
	public static void mergeAttributes(Node destinationNode, Node sourceNode, boolean replace) throws PortalException
	{
		NamedNodeMap destinationAttributes = destinationNode.getAttributes();
		NamedNodeMap sourceAttributes = sourceNode.getAttributes();

		for (int i = 0; i < sourceAttributes.getLength(); i++)
		{
			Node curAttribute = sourceAttributes.item(i);
			String curAttributeName = curAttribute.getNodeName();
			if (replace || destinationAttributes.getNamedItem(curAttributeName) == null)
				XmlUtil.addNodeAttr(destinationNode, curAttributeName, XmlUtil.getTextContent(curAttribute));
		}
	}

	/**
	 * Merges the children elements from a node to another.
	 * @param destinationNode the destination node, where the elements will be
	 * appended or replaced.
	 * @param sourceNode the source node, where the elements will be copied from. 
	 * @param replace if true, elements with the same name will be replaced;
	 * if false they will not be copied if an element in the destinationNode
	 * with the same name already exists.
	 * @since 4.0.10
	 */
	public static void mergeElements(Node destinationNode, Node sourceNode, boolean replace) throws PortalException
	{
		for (Node child = sourceNode.getFirstChild(); child != null; child = child.getNextSibling())
		{
			if (child.getNodeType() == Node.ELEMENT_NODE)
			{
				Node destinationChild = XmlUtil.selectSingleNode(child.getNodeName(), destinationNode);
				if (destinationChild == null)
				{
					Node childClone = destinationNode.getOwnerDocument().importNode(child, true);
					destinationNode.appendChild(childClone);
				}
				else
				{
					if (replace)
					{
						Node childClone = destinationNode.getOwnerDocument().importNode(child, true);
						destinationNode.replaceChild(childClone, destinationChild);
					}
				}
			}
		}
	}
	
	/**
	 * Encodes a string to be used inside a XML document. Special XML characters are converted to
	 * their corresponding entities.
	 * @return the encoded string, or an empty string if pStr is null.
	 */
	public static String encodeXml(String pStr)
	{
		if (pStr != null)
			return TextUtil.stringReplace(TextUtil.stringReplace(TextUtil.stringReplace(TextUtil.stringReplace(pStr, "&", "&amp;"), "<", "&lt;"), ">", "&gt;"), "\"", "&quot;");  
		else
			return "";
	}

	/**
	 * Returns the XML encoding for the xml with the given String.
	 * <p>
	 * The encoding is read from the XML declaration.
	 * @param xmlString the xml.
	 * @return the encoding or null if it was not found.
	 */
	public static String getXmlEncoding(String xmlString)
	{
		// use only the first tag for the encoding search
		int tagEnd = xmlString.indexOf('>');
		if (tagEnd != -1)
		{
			String tagString = xmlString.substring(0, tagEnd + 1);
			
			// search for the encoding pattern
			Matcher matcher = encodingPattern.matcher(tagString);
			if (matcher.find())
				return matcher.group(1);
		}
		return null;
	}
	
	public static boolean isValidXmlString(String xml)
	{
		// get the xml document
		try
		{
			XmlUtil.getDocument(xml);
		}
		catch (PortalException e)
		{
			return false;
		}
		return true;
	}
}